Micron Document
🎖️GitЯра🎖️

Commit be6f2cfb71c3a37ba1a1e5bbd92211a36dbe96b2


Parents : 1854bc8
Author : James Rich <2199651+jamesarich@users.noreply.github.com>
Signature : Signature validation error
Date : 2026-06-01T14:36:03-05:00
Committer : GitHub <noreply@github.com>
Date : 2026-06-01T19:36:03Z

fix(map): remove manual ViewTree lifecycle owner workarounds (#5704)

Changes
Diff

diff --git a/androidApp/src/google/kotlin/org/meshtastic/app/map/component/NodeClusterMarkers.kt b/androidApp/src/google/kotlin/org/meshtastic/app/map/component/NodeClusterMarkers.kt
index a7ae3b8e20..567e146cc6 100644
--- a/androidApp/src/google/kotlin/org/meshtastic/app/map/component/NodeClusterMarkers.kt
+++ b/androidApp/src/google/kotlin/org/meshtastic/app/map/component/NodeClusterMarkers.kt
@@ -17,18 +17,7 @@
package org.meshtastic.app.map.component
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalView
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.compose.LocalLifecycleOwner
-import androidx.lifecycle.compose.currentStateAsState
-import androidx.lifecycle.findViewTreeLifecycleOwner
-import androidx.lifecycle.setViewTreeLifecycleOwner
-import androidx.savedstate.compose.LocalSavedStateRegistryOwner
-import androidx.savedstate.findViewTreeSavedStateRegistryOwner
-import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.google.maps.android.clustering.Cluster
import com.google.maps.android.clustering.view.DefaultClusterRenderer
import com.google.maps.android.compose.Circle
@@ -47,50 +36,6 @@ fun NodeClusterMarkers(
navigateToNodeDetails: (Int) -> Unit,
onClusterClick: (Cluster<NodeClusterItem>) -> Boolean,
) {
- val view = LocalView.current
- val lifecycleOwner = LocalLifecycleOwner.current
- val savedStateRegistryOwner = LocalSavedStateRegistryOwner.current
- val lifecycleState by lifecycleOwner.lifecycle.currentStateAsState()
-
- // Workaround for https://github.com/googlemaps/android-maps-compose/issues/858
- // and https://github.com/googlemaps/android-maps-compose/issues/875
- // The maps clustering library creates an internal ComposeView to snapshot markers.
- // If that view is not attached to the hierarchy (which it often isn't during rendering),
- // it fails to find the Lifecycle and SavedState owners. We propagate them to the root view
- // so the internal snapshot view can find them when walking up the tree.
- // DisposableEffect runs at composition time (not post-composition like SideEffect),
- // ensuring owners are set before the Clustering composable triggers marker rendering.
- // Capture and restore the previous owners on dispose. The owners here are NavEntry-scoped
- // (transient) lifecycles; leaving one attached to the activity root view after this screen is
- // destroyed makes subsequently opened Popups/DropdownMenus inherit a DESTROYED lifecycle and
- // render at 0x0 (invisible). See the node-list popup regression and InlineMap.
- DisposableEffect(lifecycleOwner, savedStateRegistryOwner) {
- val root = view.rootView
- val prevRootLifecycleOwner = root.findViewTreeLifecycleOwner()
- val prevRootSavedStateRegistryOwner = root.findViewTreeSavedStateRegistryOwner()
- root.setViewTreeLifecycleOwner(lifecycleOwner)
- root.setViewTreeSavedStateRegistryOwner(savedStateRegistryOwner)
- // Also set on the view itself in case the internal renderer walks from a child
- val prevViewLifecycleOwner = view.findViewTreeLifecycleOwner()
- val prevViewSavedStateRegistryOwner = view.findViewTreeSavedStateRegistryOwner()
- if (view !== root) {
- view.setViewTreeLifecycleOwner(lifecycleOwner)
- view.setViewTreeSavedStateRegistryOwner(savedStateRegistryOwner)
- }
- onDispose {
- root.setViewTreeLifecycleOwner(prevRootLifecycleOwner)
- root.setViewTreeSavedStateRegistryOwner(prevRootSavedStateRegistryOwner)
- if (view !== root) {
- view.setViewTreeLifecycleOwner(prevViewLifecycleOwner)
- view.setViewTreeSavedStateRegistryOwner(prevViewSavedStateRegistryOwner)
- }
- }
- }
-
- // Guard against the cluster renderer's async Handler trying to render markers
- // after the lifecycle has stopped — the internal ComposeView requires an active lifecycle.
- if (!lifecycleState.isAtLeast(Lifecycle.State.STARTED)) return
-
Clustering(
items = nodeClusterItems,
onClusterClick = onClusterClick,

diff --git a/androidApp/src/google/kotlin/org/meshtastic/app/node/component/InlineMap.kt b/androidApp/src/google/kotlin/org/meshtastic/app/node/component/InlineMap.kt
index 53af32bb01..f77f8aec18 100644
--- a/androidApp/src/google/kotlin/org/meshtastic/app/node/component/InlineMap.kt
+++ b/androidApp/src/google/kotlin/org/meshtastic/app/node/component/InlineMap.kt
@@ -18,17 +18,9 @@ package org.meshtastic.app.node.component
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.key
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalView
-import androidx.lifecycle.compose.LocalLifecycleOwner
-import androidx.lifecycle.findViewTreeLifecycleOwner
-import androidx.lifecycle.setViewTreeLifecycleOwner
-import androidx.savedstate.compose.LocalSavedStateRegistryOwner
-import androidx.savedstate.findViewTreeSavedStateRegistryOwner
-import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.google.android.gms.maps.model.CameraPosition
import com.google.android.gms.maps.model.LatLng
import com.google.maps.android.compose.Circle
@@ -52,31 +44,6 @@ fun InlineMap(node: Node, modifier: Modifier = Modifier) {
true -> ComposeMapColorScheme.DARK
else -> ComposeMapColorScheme.LIGHT
}
-
- // Defensive workaround: propagate ViewTreeLifecycleOwner to the root view so that
- // any internal maps-compose ComposeView (e.g., info windows) can find the lifecycle
- // when walking up the view tree.
- //
- // IMPORTANT: capture and restore the previous owners on dispose. This InlineMap is hosted inside the
- // node-detail NavEntry, whose LocalLifecycleOwner is a transient, entry-scoped lifecycle. Leaving it
- // attached to the activity root view after the entry is destroyed (e.g. navigating back to the node
- // list) would make subsequently opened Popups/DropdownMenus inherit a DESTROYED lifecycle and
- // render at 0x0 (invisible). See the node-list popup regression.
- val view = LocalView.current
- val lifecycleOwner = LocalLifecycleOwner.current
- val savedStateRegistryOwner = LocalSavedStateRegistryOwner.current
- DisposableEffect(lifecycleOwner, savedStateRegistryOwner) {
- val root = view.rootView
- val prevRootLifecycleOwner = root.findViewTreeLifecycleOwner()
- val prevRootSavedStateRegistryOwner = root.findViewTreeSavedStateRegistryOwner()
- root.setViewTreeLifecycleOwner(lifecycleOwner)
- root.setViewTreeSavedStateRegistryOwner(savedStateRegistryOwner)
- onDispose {
- root.setViewTreeLifecycleOwner(prevRootLifecycleOwner)
- root.setViewTreeSavedStateRegistryOwner(prevRootSavedStateRegistryOwner)
- }
- }
-
key(node.num) {
val location = LatLng(node.latitude, node.longitude)
val cameraState = rememberCameraPositionState {

Served by rngit 1.4.2 - Generated in 0.07s